package com.bitcola.chainserver.server.trx;

import com.alibaba.fastjson.JSONObject;
import com.bitcola.chainserver.chain.trx.api.ContractEvent;
import com.bitcola.chainserver.chain.trx.api.Record;
import com.bitcola.chainserver.chain.trx.api.TRXTransform;
import com.bitcola.chainserver.chain.trx.api.TransactionResult;
import com.bitcola.chainserver.chain.trx.config.Constant;
import com.bitcola.chainserver.chain.trx.core.Credential;
import com.bitcola.chainserver.chain.trx.core.Trc20;
import com.bitcola.chainserver.chain.trx.core.TronApi;
import com.bitcola.chainserver.chain.trx.core.TronKit;
import com.bitcola.chainserver.chain.trx.service.AddressService;
import com.bitcola.chainserver.chain.trx.service.TransactionService;
import com.bitcola.chainserver.config.SpringContextsUtil;
import com.bitcola.chainserver.constant.DepositStatusConstant;
import com.bitcola.chainserver.controller.ChainSendMessage;
import com.bitcola.chainserver.entity.ColaChainEthKey;
import com.bitcola.chainserver.mapper.ColaChainDepositMapper;
import com.bitcola.chainserver.mapper.ColaChainEthKeyMapper;
import com.bitcola.chainserver.mapper.ColaChainKeyMapper;
import com.bitcola.chainserver.server.BaseChainServer;
import com.bitcola.chainserver.util.AESUtil;
import com.bitcola.security.common.msg.ColaChainBalance;
import com.bitcola.security.common.msg.ColaChainDepositResponse;
import com.bitcola.security.common.msg.ColaChainWithdrawResponse;
import com.google.gson.Gson;
import lombok.extern.log4j.Log4j2;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.web3j.protocol.core.methods.response.Transaction;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

@Service
@Log4j2
public class TrxServer extends BaseChainServer {
    public static ExecutorService executor = Executors.newCachedThreadPool();
    @Autowired
    RedisTemplate<String, Object> redisTemplate;
    Map<String, String> addresses = new ConcurrentHashMap<>();
    volatile static long lastBlockNumber = 0;
    @Autowired
    AddressService addressService;
    @Autowired
    TransactionService service;
    Map<String, Integer> unConfirm = new ConcurrentHashMap<>();
    BlockingQueue<Long> unSuccessNumber = new LinkedBlockingQueue<>();
    Map<String, String> recordMap = new ConcurrentHashMap<>();

    @Autowired
    ColaChainEthKeyMapper ethKeyMapper;
    @Autowired
    ColaChainDepositMapper depositMapper;
    List<String> listenedContractAddress = new CopyOnWriteArrayList<>();

    @Value("${trx.hot_address}")
    String hotAddress;


    public String getHotAddressPrivateKey() {
        return getPrivateKeybyAddress(hotAddress);
    }

    @Nullable
    private String getPrivateKeybyAddress(String address) {
        ColaChainEthKey ethKey = ethKeyMapper.selectByPrimaryKey(address);
        if (ethKey != null) {
            String privateKey = AESUtil.decrypt(ethKey.getPrivateKey(), encoderKey);
            return privateKey;
        }
        return null;
    }


    @Override
    public String getModuleName() {
        return "TRX";
    }

    protected List<String> getAddress() {
        ChainSendMessage bean = SpringContextsUtil.applicationContext.getBean(ChainSendMessage.class);
        var list = bean.getAddress(getModuleName());
        return list;
    }

    @Value("${bitcola.chain.password}")
    String encoderKey;

    public void updateLastBlockNumber() {
        executor.submit(() -> {
            var tronAPi = new TronApi();
            while (true) {
                try {
                    lastBlockNumber = tronAPi.getLastNlock();
                    Thread.sleep(1000);
                } catch (Exception e) {

                }
            }
        });
    }

    public long getCurrentNumber() throws Exception {
        var value = redisTemplate.opsForValue().get("blockNumbers");
        if (value == null || value.toString().equals("0")) {
            var tronAPi = new TronApi();
            long number = tronAPi.getLastNlock();
            redisTemplate.opsForValue().set("blockNumbers", number);
            return number;
        }
        return Long.parseLong(value + "");
    }

    @Override
    protected void run() throws Throwable {
        executor.submit(() -> {
            log.info("TRON模块正在启动");
            TronKit kit = new TronKit(
                    TronApi.mainNet(),
                    Credential.fromPrivateKey("8D9142B97B38F992B4ADF9FB3D0DD527B1F47BE113C6D0B5C32A0571EF1E7B5F")
            );


            updateLastBlockNumber();
            executor.submit(() -> {
                getAddress().forEach(a -> {
                    addresses.put(a, a);
                });
                var tronAPi = new TronApi();

                List<String> as = new ArrayList<>();
                for (String a : addresses.keySet()) {
                    Trc20 token = kit.trc20("TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");
                    var value = token.balanceOf(a);
                    if (value.compareTo(BigInteger.ONE) >= 0) {
                        System.out.println(a);
                        as.add(a);


                    }

                }
                System.out.println(JSONObject.toJSONString(as));
                long currentNum = getCurrentNumber();
                List<ColaChainDepositResponse> unConfirmDeposit = depositMapper.unConfirm(getModuleName());
                unConfirmDeposit.forEach(x -> {
                    unConfirm.put(x.getHash(), +1);
                });
                while (true) {
                    tronAPi = new TronApi();
                    try {


                        var api = lastBlockNumber;
                        if (currentNum > 0) {
                            while (api > currentNum) {

                                currentNum = getCurrentNumber();
//                            System.out.println("scan: " + currentNum + "-----------------------");
                                TronApi finalTronAPi = tronAPi;
                                long finalCurrentNum = currentNum;
                                if (finalCurrentNum % 20 == 0) log.info("扫描trx区块高度" + finalCurrentNum);
                                executor.submit(() -> {
                                    try {

                                        recordNumber(finalCurrentNum);

                                        scanTokenByNumber(finalTronAPi, finalCurrentNum);
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }
                                });
                                currentNum++;
                                redisTemplate.opsForValue().set("blockNumbers", currentNum);

                            }

                            if (!(api > currentNum)) {
                                currentNum = lastBlockNumber;
                                redisTemplate.opsForValue().set("blockNumbers", currentNum);
                                Thread.sleep(4000);

                            }
                        }
                    } catch (Exception e) {
                        unSuccessNumber.add(currentNum);
                        e.printStackTrace();
                    }

                }
            });

        });


    }

    private void scanTransactionByNumber(TronApi tronAPi, long currentNum) throws Exception {
        var list = tronAPi.getTransformsByBlockNumber(currentNum);
        scanBlock(list);
    }

    public void scanBlock(List<TRXTransform> transforms) {
        for (TRXTransform trxTransform : transforms) {
            if (addresses.containsKey(trxTransform.to)) {
                try {
                    addNewRecord(trxTransform.txid, trxTransform.to, null, trxTransform.value, trxTransform.block_no, trxTransform.block_no, trxTransform.from);
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }
        }
    }

    Timer timer = new Timer();

    public void scanTokenByNumber(TronApi tronAPi, long currentNum) throws Exception {
//        System.out.println("扫描: " + currentNum);
//        System.out.println("扫描: " + currentNum);
        var list = tronAPi.getBlockEvents(currentNum);
        list = list.stream().filter(x -> x.eventName.equals("Transfer")).collect(Collectors.toList());
        scanTokenEvent(list);
    }

    private void scanTokenEvent(List<ContractEvent> events) {
        for (ContractEvent event : events) {

            String contractAddress = event.contractAddress;
            event.resultType.forEach((k, v) -> {
                        if (k.equals("to")) {
                            String toAddress = null;
                            try {
                                toAddress = event.result.get(k).toString();
                            } catch (Exception e) {
                                System.out.println(new Gson().toJson(event));
                            }
                            if (toAddress == null) {
                                System.out.println(new Gson().toJson(event));
                            } else {
                                if (addresses.containsKey(toAddress)) {
                                    Object value = event.result.get("value");
                                    if (StringUtils.isEmpty(value)) value = event.result.get("amount");
                                    BigDecimal amount = new BigDecimal(value.toString());

                                    try {
                                        String from = null;
                                        if (event.result.get("from") != null) from = event.result.get("from").toString();
                                        addNewRecord(event.transactionId, toAddress, event.contractAddress, amount, event.blockNumber, event.blockNumber, from);
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }


                                }
                            }

                        }
                    }
            );
        }

    }

    @Override
    public String newAccount(String coinCode) throws Throwable {
        var map = addressService.newAddress();
        addresses.put(map.get("address"), map.get("address"));
        return map.get("address");
    }

    public TransactionResult transformToken(String privateKey, String toAddress, BigDecimal amount, String contractAddress) throws Exception {
        TronKit kit = new TronKit(
                TronApi.mainNet(),
                Credential.fromPrivateKey(privateKey)
        );
        Trc20 token = kit.trc20(contractAddress);
        var decimals = token.decimals();
        TransactionResult ret = token.transfer(toAddress, BigInteger.valueOf(amount.multiply(new BigDecimal(Math.pow(10, decimals.longValue()))).longValue()));
        return ret;

    }


    @Autowired
    TransactionService transactionService;

    @Override
    public boolean checkAddress(String address) {

        return address.startsWith("T") || address.startsWith("t");
    }

    public void addNewRecord(String txid, String toAddress, String contractAddress, BigDecimal amount, long blockNumber, long currentBlockNumber, String from) throws Exception {
        TronKit kit = new TronKit(
                TronApi.mainNet(),
                Credential.fromPrivateKey("8D9142B97B38F992B4ADF9FB3D0DD527B1F47BE113C6D0B5C32A0571EF1E7B5F")
        );
        Trc20 token = kit.trc20(contractAddress);

        Record record = new Record();
        record.txid = txid;
        record.address = toAddress;
        record.contractAddress = contractAddress;
        record.bockNumber = blockNumber;
        record.currentBockNumber = currentBlockNumber;
        if (!StringUtils.isEmpty(contractAddress)) {
            var decimal = token.decimals();
            amount = amount.divide(BigDecimal.valueOf(Math.pow(10l, decimal.longValue())), 10, RoundingMode.DOWN);
        } else {
            amount = amount.divide(BigDecimal.valueOf(Constant.SUN), 10, RoundingMode.DOWN);
        }
        record.amount = amount;
        if (contractAddress.toLowerCase().equals("TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t".toLowerCase())) {
            if (true) {
                ColaChainDepositResponse deposit = createDepositEntity(txid, amount, "USDT", getModuleName(),
                        DepositStatusConstant.NOT_CONFIRM, toAddress, from, null, currentBlockNumber);
                String orderId = deposit(deposit);
                if (org.apache.commons.lang3.StringUtils.isNotBlank(orderId)) {
                    unConfirm.put(txid, 0);
                    // 转移热钱包
                    BigDecimal finalAmount = amount;
                    BigDecimal finalAmount1 = amount;
                    cachedThreadPool.submit(() -> {

                        try {
                            var amountTrx = addressService.getBalance(toAddress, null);
                            if (amountTrx.compareTo(BigDecimal.valueOf(0.6)) < 0) transactionService.transformTrx(getHotAddressPrivateKey(), toAddress, 600000);
                            timer.schedule(new TimerTask() {
                                @Override
                                public void run() {
                                    try {
                                        transformToken(getPrivateKeybyAddress(toAddress), hotAddress, finalAmount1, "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");
                                    } catch (Exception e) {
                                        e.printStackTrace();
                                    }

                                }
                            }, 60000);

                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    });
                }
            }
//        redisTemplate.opsForHash().put(Constant.UN_CONFIRM_QUEUE, txid, record);
        }
    }

    @Override
    public ColaChainWithdrawResponse withdraw(String from, String coinCode, String address, BigDecimal number, String memo) throws Throwable {
        ColaChainWithdrawResponse response = new ColaChainWithdrawResponse();
        TransactionResult result = null;
        try {
            result = transformToken(getHotAddressPrivateKey(), address, number, "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");
        } catch (Exception e) {
            response.setErrMessage(e.toString());
            response.setSuccess(false);
        }
        if (result.state) {
            response.setFee(BigDecimal.ZERO);
            response.setFeeCoinCode("trx");
            response.setHash(result.txId);
            response.setSuccess(true);
        } else {
            response.setErrMessage(result.message);
            response.setSuccess(false);
        }
        return response;

    }

    @Override
    public void confirm() throws Throwable {

    }

    @Override
    public ColaChainBalance getChainBalance(String coinCode, String feeCoinCode) throws Throwable {
        return null;
    }

    @Scheduled(cron = "0 0/5 * * * ?")
    public void scanError() {

        executor.submit(() -> {
            while (true) {
                long temp = 0;
                try {
                    Thread.sleep(6000);
                    var tronAPi = new TronApi();
                    long currentNum = unSuccessNumber.take();
                    System.out.println("扫描错误区块:" + currentNum);
//                    scanTransactionByNumber(tronAPi, currentNum);
                    scanTokenByNumber(tronAPi, currentNum);
                    temp = currentNum;

                } catch (Exception e) {
                    unSuccessNumber.add(temp);
                    e.printStackTrace();
                }

            }
        });


    }

    public void recordNumber(long number) throws Exception {
        Iterator<String> iterator = unConfirm.keySet().iterator();
        while (iterator.hasNext()) {
            try {
                String hash = iterator.next();
                Integer oldConfirm = unConfirm.get(hash);
                ColaChainDepositResponse colaChainDepositResponse = depositMapper.selectByPrimaryKey(hash);
                String orderId = colaChainDepositResponse.getOrderId();
                if (oldConfirm + 1 >= super.getConfirmNumber(colaChainDepositResponse.getCoinCode())) {

                    var result = service.getTransactionById(hash);

                    if (result != null) {
                        iterator.remove();
                        colaChainDepositResponse.setStatus(DepositStatusConstant.CONFIRM);
                        depositMapper.updateByPrimaryKeySelective(colaChainDepositResponse);
                        completeDeposit(orderId);

                    }
                } else {
                    if (orderId != null) {
                        confirmNumber(oldConfirm + 1, orderId);
                        unConfirm.put(hash, oldConfirm + 1);
                    } else {
                        log.info(JSONObject.toJSONString(colaChainDepositResponse));
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }


    @Scheduled(cron = "0 0 0/1 * * ? ")
    public void transfer() throws Exception {
        log.info("======================= eth 自动热钱包转移开始 =========================");
        List<Map<String, String>> result = ethKeyMapper.
                getTransferInfo(System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000L), System.currentTimeMillis());
        log.info("最新一个月充值数:" + result.size());
        int i = 0;
        TronKit kit = new TronKit(
                TronApi.mainNet(),
                Credential.fromPrivateKey(getHotAddressPrivateKey())
        );
        Trc20 token = kit.trc20("TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");
        BigInteger decimal = token.decimals();
        for (Map<String, String> map : result) {

            String coin = map.get("coin").toString();
            String address = map.get("address").toString();
            String contract = map.get("contract");
            log.info(address + " 币种:" + coin);


            if (address.startsWith("T")) {
                var balance = token.balanceOf(address);
                if (balance.compareTo(BigInteger.ZERO) > 0) {
                    ColaChainEthKey ethKey = ethKeyMapper.selectByPrimaryKey(address);
                    if (ethKey != null) {
                        String privateKey = AESUtil.decrypt(ethKey.getPrivateKey(), encoderKey);

                        var amountTrx = addressService.getBalance(address, null);
                        if (amountTrx.compareTo(BigDecimal.valueOf(0.6)) < 0) {

                            try {
                                transactionService.transformTrx(getHotAddressPrivateKey(), address, 600000);
                                continue;
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                        if (privateKey != null) {
                            BigDecimal amount = new BigDecimal(balance.toString()).divide(BigDecimal.valueOf(Math.pow(10l, decimal.longValue())), 10, RoundingMode.DOWN);

                            transformToken(privateKey, hotAddress, amount, "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t");
                        }
                    }
                }
            }

        }
        log.info("======================= trx 自动热钱包转移结束 =========================");
    }


    public static void main(String[] args) throws Exception {
        String usdt = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
        String[] address = {"TZHPzgf6BADAZSdDFxM8erdfhges78eb2X"


                , "T9zqkpx4yVBJqUkcLjnGLthcj3kTq68dmU"
                , "T9yn8tGJVkmPcX2gAhDcQRG2cj9qBa89UF"};

        TronKit kit = new TronKit(
                TronApi.mainNet(),
                Credential.fromPrivateKey("8D9142B97B38F992B4ADF9FB3D0DD527B1F47BE113C6D0B5C32A0571EF1E7B5F")
        );
        var token = kit.trc20(usdt);

        BigInteger sum = BigInteger.valueOf(0);
        for (String a : address) {
            var value = token.balanceOf(a);
            sum = sum.add(value);
        }

        System.out.println(new BigDecimal(sum.toString()).divide(BigDecimal.valueOf(Constant.SUN)));


    }

}
